package upload

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"gitee.com/freedom_feel/gui-bos-api/global"
	"gitee.com/freedom_feel/gui-bos-api/logs"
	"github.com/gin-gonic/gin"
	"io"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// 内存进度存储（记录大文件上传进度）
var (
	progressMap = make(map[string]*UpdProgress)
	progressMu  sync.RWMutex
)

// 上传进度结构体（大文件分块用）
type UpdProgress struct {
	FileMD5    string           // 文件唯一标识（MD5）
	FileName   string           // 原始文件名
	FileSize   int64            // 文件总大小
	ChunkSize  int64            // 分块大小
	Uploaded   int64            // 已上传字节数
	Chunks     map[int]struct{} // 已上传分块编号
	Status     string           // 状态(uploading/completed/failed)
	ExpireTime time.Time        // 进度过期时间
}

// 智能上传处理器：单file参数，自动适配大/小文件
func SmartUploadHandler(c *gin.Context) {
	// 限制最大请求大小
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, global.MaxUploadSize)

	// 获取文件
	file, handler, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": fmt.Sprintf("获取文件失败（请确保参数名为file）: %v", err),
		})
		return
	}
	defer file.Close()

	logs.Info("logger2")

	// 计算文件MD5
	fileMD5, err := calculateFileMD5(file)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("文件标识计算失败: %v", err),
		})
		return
	}

	// 重置文件指针
	if _, err := file.Seek(0, 0); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("文件指针重置失败: %v", err),
		})
		return
	}

	logs.Info("logger3")

	// 自动适配：根据文件大小选择「简单上传」或「分块上传」
	fileSize := handler.Size
	if fileSize <= global.ChunkSize {
		logs.Info("文件小于10MB，简单上传")
		handleSmallFileUpload(c, file, handler, fileMD5)
	} else {
		logs.Info("文件大于10MB，自动分块+断点续传")
		handleLargeFileUpload(c, file, handler, fileMD5, fileSize)
	}
	return
}

// 小文件处理（<=10MB）：整体上传，直接保存
func handleSmallFileUpload(c *gin.Context, file io.Reader, handler *multipart.FileHeader, fileMD5 string) {
	// 生成唯一文件名
	saveFileName := generateUniqueFileName(handler.Filename, fileMD5)
	saveFilePath := filepath.Join(global.SaveDir, saveFileName)

	// 生成可访问的HTTP URL
	accessURL := fmt.Sprintf("%s%s%s", getServerBaseURL(c.Request), global.StaticPath, saveFileName)

	// 检查文件是否已存在
	if _, err := os.Stat(saveFilePath); err == nil {
		sendSuccessResponse(c, "文件已存在（无需重复上传）", handler.Filename, saveFileName, accessURL)
		return
	}

	// 创建文件并写入内容
	dstFile, err := os.Create(saveFilePath)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("创建文件失败: %v", err),
		})
		return
	}
	defer dstFile.Close()

	if _, err := io.Copy(dstFile, file); err != nil {
		os.Remove(saveFilePath) // 写入失败，删除空文件
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("保存文件失败: %v", err),
		})
		return
	}

	//common.Success(c, "", map[string]string{
	//	"status":             "success",
	//	"message":            "小文件上传成功",
	//	"original_file_name": handler.Filename,
	//	"saved_file_name":    saveFileName,
	//	"access_url":         accessURL,
	//	"tip":                "直接复制access_url到浏览器即可访问文件",
	//})

	// 返回成功响应
	sendSuccessResponse(c, "小文件上传成功", handler.Filename, saveFileName, accessURL)
	return
}

// 大文件处理（>10MB）：自动分块+断点续传（用户无感知）
func handleLargeFileUpload(c *gin.Context, file io.Reader, handler *multipart.FileHeader, fileMD5 string, fileSize int64) {
	// 1. 初始化/加载上传进度
	progress := getOrInitProgress(fileMD5, handler.Filename, fileSize)
	if progress.Status == "completed" {
		// 文件已上传完成，直接返回URL
		saveFileName := generateUniqueFileName(handler.Filename, fileMD5)
		accessURL := fmt.Sprintf("%s%s%s", getServerBaseURL(c.Request), global.StaticPath, saveFileName)
		sendSuccessResponse(c, "文件已上传完成（断点续传）", handler.Filename, saveFileName, accessURL)
		return
	}

	// 2. 临时分块目录（按文件MD5隔离）
	tempChunkDir := filepath.Join(os.TempDir(), "upload_chunks", fileMD5)
	os.MkdirAll(tempChunkDir, 0755)

	// 3. 分块读取并保存（自动跳过已上传分块）
	var (
		chunkNum  = 0
		totalRead = int64(0)
		buf       = make([]byte, global.ChunkSize)
	)

	for {
		n, err := file.Read(buf)
		if err != nil && err != io.EOF {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": fmt.Sprintf("读取文件分块失败: %v", err),
			})
			return
		}
		if n == 0 {
			break // 读取完毕
		}
		totalRead += int64(n)

		// 跳过已上传的分块
		progressMu.RLock()
		_, isUploaded := progress.Chunks[chunkNum]
		progressMu.RUnlock()
		if isUploaded {
			chunkNum++
			continue
		}

		// 保存当前分块到临时目录
		chunkPath := filepath.Join(tempChunkDir, fmt.Sprintf("chunk_%d.bin", chunkNum))
		if err := os.WriteFile(chunkPath, buf[:n], 0644); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": fmt.Sprintf("保存分块%d失败: %v", chunkNum, err),
			})
			return
		}

		// 更新上传进度
		progressMu.Lock()
		progress.Chunks[chunkNum] = struct{}{}
		progress.Uploaded += int64(n)
		progress.ExpireTime = time.Now().Add(global.ProgressTTL) // 延长过期时间
		progressMu.Unlock()

		chunkNum++
	}

	// 4. 检查是否所有分块都已上传
	progressMu.RLock()
	totalChunks := (fileSize + global.ChunkSize - 1) / global.ChunkSize // 总分块数（向上取整）
	isAllUploaded := len(progress.Chunks) == int(totalChunks)
	progressMu.RUnlock()

	if !isAllUploaded {
		// 未上传完：返回进度（支持断点续传）
		progressMu.RLock()
		progressPercent := float64(progress.Uploaded) / float64(fileSize) * 100
		progressMu.RUnlock()

		c.JSON(http.StatusPartialContent, gin.H{
			"status":    "progress",
			"message":   "大文件分块上传中（支持断点续传）",
			"file_name": handler.Filename,
			"file_size": fileSize,
			"uploaded":  progress.Uploaded,
			"progress":  fmt.Sprintf("%.1f%%", progressPercent),
		})
		return
	}

	// 5. 所有分块上传完：合并分块为完整文件
	saveFileName := generateUniqueFileName(handler.Filename, fileMD5)
	saveFilePath := filepath.Join(global.SaveDir, saveFileName)
	accessURL := fmt.Sprintf("%s%s%s", getServerBaseURL(c.Request), global.StaticPath, saveFileName)

	if err := mergeChunks(tempChunkDir, saveFilePath, totalChunks); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("合并分块失败: %v", err),
		})
		return
	}

	// 6. 更新进度为"已完成"，清理临时分块
	progressMu.Lock()
	progress.Status = "completed"
	progressMu.Unlock()
	os.RemoveAll(tempChunkDir)

	// 7. 返回成功响应
	sendSuccessResponse(c, "大文件分块上传完成", handler.Filename, saveFileName, accessURL)
	return
}

// 辅助函数：计算文件MD5（作为唯一标识）
func calculateFileMD5(file io.Reader) (string, error) {
	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}
	return hex.EncodeToString(hash.Sum(nil))[:16], nil // 取前16位，兼顾唯一性和简洁性
}

// 辅助函数：生成唯一文件名（原文件名+MD5前缀+时间戳，避免重名）
func generateUniqueFileName(originalName, fileMD5 string) string {
	ext := filepath.Ext(originalName)                     // 扩展名（如.png）
	baseName := originalName[:len(originalName)-len(ext)] // 文件名（不含扩展名）
	timestamp := time.Now().Format("20060102150405")      // 时间戳（防重名）
	return fmt.Sprintf("%s_%s_%s%s", baseName, timestamp, fileMD5, ext)
}

// 辅助函数：初始化/加载上传进度（断点续传用）
func getOrInitProgress(fileMD5, fileName string, fileSize int64) *UpdProgress {
	progressMu.Lock()
	defer progressMu.Unlock()

	// 1. 检查是否已有进度，且未过期
	progress, exists := progressMap[fileMD5]
	if exists && time.Now().Before(progress.ExpireTime) {
		return progress
	}

	// 2. 初始化新进度
	newProgress := &UpdProgress{
		FileMD5:    fileMD5,
		FileName:   fileName,
		FileSize:   fileSize,
		ChunkSize:  global.ChunkSize,
		Uploaded:   0,
		Chunks:     make(map[int]struct{}),
		Status:     "uploading",
		ExpireTime: time.Now().Add(global.ProgressTTL),
	}
	progressMap[fileMD5] = newProgress
	return newProgress
}

// 辅助函数：合并分块为完整文件
func mergeChunks(chunkDir, outputPath string, totalChunks int64) error {
	outputFile, err := os.Create(outputPath)
	if err != nil {
		return err
	}
	defer outputFile.Close()

	// 按分块编号顺序合并
	for i := int64(0); i < totalChunks; i++ {
		chunkPath := filepath.Join(chunkDir, fmt.Sprintf("chunk_%d.bin", i))
		chunkData, err := os.ReadFile(chunkPath)
		if err != nil {
			return fmt.Errorf("读取分块%d: %v", i, err)
		}

		if _, err := outputFile.Write(chunkData); err != nil {
			return fmt.Errorf("写入分块%d: %v", i, err)
		}
	}
	return nil
}

// 辅助函数：获取服务基础URL（用于生成可访问URL）
func getServerBaseURL(r *http.Request) string {
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	host := r.Host

	// 如果host不包含端口，添加默认端口
	if _, _, err := net.SplitHostPort(host); err != nil {
		// 从请求中获取实际端口
		if scheme == "https" {
			host = fmt.Sprintf("%s:%s", host, "443")
		} else {
			host = fmt.Sprintf("%s:%s", host, "80")
		}
	}

	return fmt.Sprintf("%s://%s", scheme, host)
}

// 辅助函数：发送成功响应
func sendSuccessResponse(c *gin.Context, message, originalName, savedName, accessURL string) {
	c.JSON(http.StatusOK, gin.H{
		"status":             "success",
		"message":            message,
		"original_file_name": originalName,
		"saved_file_name":    savedName,
		"access_url":         accessURL,
		"tip":                "直接复制access_url到浏览器即可访问文件",
	})
}
